Skip to main content

copp\copp\copp2/
formulation.rs

1//! Problem data models and builders for second-order path parameterization.
2//!
3//! # Method identity
4//! This module defines validated formulation objects for:
5//! - **Time-Optimal Path Parameterization (TOPP2)**,
6//! - **Convex-Objective Path Parameterization (COPP2)**.
7//!
8//! # Discrete variables (shared notation)
9//! On a station grid with closed index interval `[idx_s_start, idx_s_final]`:
10//! - state profile is `a(s)=\dot{s}^2`;
11//! - boundary tuple is `a_boundary = (a_start, a_final)`;
12//! - station count is `s_len = idx_s_final - idx_s_start + 1`.
13//!
14//! # High-level pipeline
15//! 1. Construct `Topp2ProblemBuilder` or `Copp2ProblemBuilder` from caller data.
16//! 2. Run builder validation (index interval, bounds, objective compatibility).
17//! 3. Build immutable problem objects used by DP/optimization backends.
18
19use crate::copp::constraints::Constraints;
20use crate::copp::{CoppObjective, validate_copp2_objectives};
21use crate::diag::{CoppError, check_non_negative, check_s_interval_valid};
22use crate::robot::robot_core::{Robot, RobotBasic, RobotTorque};
23
24/// Formulated TOPP2 problem data.
25///
26/// # Fields
27/// - `constraints`: path-dependent kinematic/dynamic bounds on the selected interval;
28/// - `idx_s_interval`: closed station-index interval `[idx_s_start, idx_s_final]`;
29/// - `a_boundary`: endpoint state tuple `(a_start, a_final)`.
30pub struct Topp2Problem<'a> {
31    pub(crate) constraints: &'a Constraints,
32    pub(crate) idx_s_interval: (usize, usize),
33    pub(crate) a_boundary: (f64, f64),
34}
35
36impl<'a> Topp2Problem<'a> {
37    /// Return station count on the closed interval.
38    ///
39    /// For `[idx_s_start, idx_s_final]`, this returns
40    /// `idx_s_final - idx_s_start + 1`.
41    #[inline]
42    pub fn s_len(&self) -> usize {
43        self.idx_s_interval.1 - self.idx_s_interval.0 + 1
44    }
45}
46
47/// Builder for [`Topp2Problem`].
48pub struct Topp2ProblemBuilder<'a> {
49    /// Reference to path constraints (extracted from robot at construction time).
50    pub constraints: &'a Constraints,
51    /// Closed station-index interval `(idx_s_start, idx_s_final)`.
52    pub idx_s_interval: (usize, usize),
53    /// Endpoint state tuple `(a_start, a_final)`.
54    pub a_boundary: (f64, f64),
55}
56
57impl<'a> Topp2ProblemBuilder<'a> {
58    /// Create a TOPP2 builder from a [`Robot`] reference.
59    ///
60    /// # Parameters
61    /// - `robot`: robot wrapper with the trait [`RobotBasic`] whose constraint buffer defines the problem domain.
62    /// - `idx_s_interval`: closed station-index interval `(idx_s_start, idx_s_final)`.
63    /// - `a_boundary`: endpoint state tuple `(a_start, a_final)`.
64    #[inline]
65    pub fn new<M: RobotBasic>(
66        robot: &'a Robot<M>,
67        idx_s_interval: (usize, usize),
68        a_boundary: (f64, f64),
69    ) -> Self {
70        Self {
71            constraints: &robot.constraints,
72            idx_s_interval,
73            a_boundary,
74        }
75    }
76
77    /// Create a TOPP2 builder with all required fields.
78    ///
79    /// # Parameters
80    /// - `constraints`: reference to path constraints defining the problem domain.
81    /// - `idx_s_interval`: closed station-index interval `(idx_s_start, idx_s_final)`.
82    /// - `a_boundary`: endpoint state tuple `(a_start, a_final)`.
83    #[inline]
84    pub fn with_constraint(
85        constraints: &'a Constraints,
86        idx_s_interval: (usize, usize),
87        a_boundary: (f64, f64),
88    ) -> Self {
89        Self {
90            constraints,
91            idx_s_interval,
92            a_boundary,
93        }
94    }
95
96    /// Build a validated [`Topp2Problem`].
97    #[inline]
98    pub fn build(&self) -> Result<Topp2Problem<'a>, CoppError> {
99        self.validate()?;
100        Ok(Topp2Problem {
101            constraints: self.constraints,
102            idx_s_interval: self.idx_s_interval,
103            a_boundary: self.a_boundary,
104        })
105    }
106
107    /// Validate builder fields and consistency.
108    #[inline]
109    pub fn validate(&self) -> Result<(), CoppError> {
110        check_s_interval_valid(
111            "Topp2ProblemBuilder",
112            self.idx_s_interval.0,
113            self.idx_s_interval.1,
114        )?;
115        self.constraints.check_s_in_bounds(
116            self.idx_s_interval.0,
117            self.idx_s_interval.1 - self.idx_s_interval.0 + 1,
118        )?;
119        check_non_negative(
120            "Topp2ProblemBuilder",
121            "a_start (a_boundary.0)",
122            self.a_boundary.0,
123        )?;
124        check_non_negative(
125            "Topp2ProblemBuilder",
126            "a_final (a_boundary.1)",
127            self.a_boundary.1,
128        )?;
129        Ok(())
130    }
131}
132
133/// Formulated COPP2 problem data.
134///
135/// # Fields
136/// - `robot`: robot model supplying constraints and torque-related terms;
137/// - `objectives`: objective list for COPP2 optimization;
138/// - `idx_s_interval`: closed station-index interval `[idx_s_start, idx_s_final]`;
139/// - `a_boundary`: endpoint state tuple `(a_start, a_final)`.
140pub struct Copp2Problem<'a, M: RobotTorque> {
141    pub(crate) robot: &'a Robot<M>,
142    pub(crate) objectives: &'a [CoppObjective<'a>],
143    pub(crate) idx_s_interval: (usize, usize),
144    pub(crate) a_boundary: (f64, f64),
145}
146
147impl<'a, M: RobotTorque> Copp2Problem<'a, M> {
148    /// Return station count on the closed interval.
149    ///
150    /// For `[idx_s_start, idx_s_final]`, this returns
151    /// `idx_s_final - idx_s_start + 1`.
152    #[inline]
153    pub fn s_len(&self) -> usize {
154        self.idx_s_interval.1 - self.idx_s_interval.0 + 1
155    }
156}
157
158/// Builder for [`Copp2Problem`].
159pub struct Copp2ProblemBuilder<'a, M: RobotTorque> {
160    /// Reference to robot model defining constraints and dynamics.
161    pub robot: &'a Robot<M>,
162    /// Closed station-index interval `(idx_s_start, idx_s_final)`.
163    pub idx_s_interval: (usize, usize),
164    /// Endpoint state tuple `(a_start, a_final)`.
165    pub a_boundary: (f64, f64),
166    /// Objectives for COPP2 optimization.
167    pub objectives: &'a [CoppObjective<'a>],
168}
169
170impl<'a, M: RobotTorque> Copp2ProblemBuilder<'a, M> {
171    /// Create a COPP2 builder with all required fields.
172    #[inline]
173    pub fn new(
174        robot: &'a Robot<M>,
175        idx_s_interval: (usize, usize),
176        a_boundary: (f64, f64),
177        objectives: &'a [CoppObjective<'a>],
178    ) -> Self {
179        Self {
180            robot,
181            idx_s_interval,
182            a_boundary,
183            objectives,
184        }
185    }
186
187    /// Build a validated [`Copp2Problem`].
188    #[inline]
189    pub fn build(&self) -> Result<Copp2Problem<'a, M>, CoppError> {
190        self.validate()?;
191        Ok(Copp2Problem {
192            robot: self.robot,
193            idx_s_interval: self.idx_s_interval,
194            a_boundary: self.a_boundary,
195            objectives: self.objectives,
196        })
197    }
198
199    /// Validate builder fields and objective compatibility.
200    #[inline]
201    pub fn validate(&self) -> Result<(), CoppError> {
202        check_s_interval_valid(
203            "Copp2ProblemBuilder",
204            self.idx_s_interval.0,
205            self.idx_s_interval.1,
206        )?;
207        self.robot.constraints.check_s_in_bounds(
208            self.idx_s_interval.0,
209            self.idx_s_interval.1 - self.idx_s_interval.0 + 1,
210        )?;
211        check_non_negative(
212            "Copp2ProblemBuilder",
213            "a_start (a_boundary.0)",
214            self.a_boundary.0,
215        )?;
216        check_non_negative(
217            "Copp2ProblemBuilder",
218            "a_final (a_boundary.1)",
219            self.a_boundary.1,
220        )?;
221
222        let s_len = self.idx_s_interval.1 - self.idx_s_interval.0 + 1;
223        validate_copp2_objectives(
224            "Copp2ProblemBuilder",
225            self.objectives,
226            self.robot.dim(),
227            s_len,
228        )?;
229        Ok(())
230    }
231}
232
233impl<'a, M: RobotTorque> Copp2Problem<'a, M> {
234    /// Convert to the TOPP2 view that shares interval and boundary fields.
235    ///
236    /// This is used by internal stages that only need standard TOPP2 constraints.
237    pub(crate) fn as_topp2_problem(&self) -> Topp2Problem<'a> {
238        Topp2Problem {
239            constraints: &self.robot.constraints,
240            idx_s_interval: self.idx_s_interval,
241            a_boundary: self.a_boundary,
242        }
243    }
244}